第一次讀 A2A 的規格,我的預期是會看到一大包東西:註冊中心、訊息路由、結果合併、重試策略。實際打開才發現它小得驚人。
A2A 定義的 method 只有四個:
| method | 做什麼 |
|---|---|
message/send |
把一則訊息送給對方,等它回覆 |
message/stream |
同上,但用串流拿回覆 |
tasks/get |
查一次委託現在的狀態 |
tasks/cancel |
取消一次委託 |
全部是單向委託與查詢。沒有廣播、沒有訂閱、沒有「把這三家的報價幫我合併」。
補一個版本上的細節,我是在讀 ADK scaffold 產出的 app_utils/a2a.py 時才發現的:上面這組帶斜線的名稱是 A2A 0.3 的寫法,1.0 換成了 PascalCase,message/send 對應 SendMessage。那份程式碼裡有一段在 A2A-Version header 被 proxy 剝掉時,靠 method 名稱有沒有斜線來反推版本。本系列後面的討論以 0.3 的名稱為主,因為手上跑過的東西都是那個版本。
Agent Card:一張放在 /.well-known/agent-card.json 的名片,寫著這隻 agent 叫什麼、能做什麼、端點在哪。要用一隻 agent,先抓它的卡。
Task:一次委託的完整狀態物件。它有狀態機,submitted、working、input-required、completed 這樣走。委託方拿到的就是這個物件。
Message / Part:訊息本體。一則 Message 由多個 Part 組成,Part 可以是文字、檔案、結構化資料。這個設計讓同一個協定能傳純文字也能傳附件。
Client / Server 角色:發起委託的是 client,接受委託的是 server。同一隻 agent 可以同時是兩者,這是多層委託能成立的原因。
光看 method 清單還抽象,我對照參考資料裡的範例,把一次 message/send 的往返拆開看。
送出去的封包大致是這樣:
{
"jsonrpc": "2.0",
"id": "1",
"method": "message/send",
"params": {
"message": {
"role": "user",
"messageId": "94a4a3e8",
"contextId": "9657f587",
"parts": [{ "kind": "text", "text": "我要兩個起司漢堡" }]
}
}
}
對方回來的東西比較有意思:
{
"id": "49d5a3fb",
"kind": "task",
"contextId": "9657f587",
"status": { "state": "completed" },
"artifacts": [
{
"artifactId": "2d58f74a",
"parts": [{ "kind": "text", "text": "已下訂 2 個起司漢堡,總計 180 元,訂單編號 69a06729。" }]
}
],
"history": []
}
三個欄位值得盯著看:status.state 標記這次委託做到哪,artifacts 放真正的成品,history 存這次往返的來回紀錄。
status.state 有五種值:submitted(收到了)、working(正在做)、input-required(缺資訊需要你補)、completed(好了)、failed(掛了)。input-required 這個狀態我覺得設計得巧,對方發現資訊不夠,可以停在這個狀態反問你,等你補完再繼續同一個 task,不用整個重來。
四個 method 裡的前兩個容易被搞混。message/send 是一問一答,等對方做完一次性回給你。message/stream 是同一件事但用串流的方式,對方一段一段吐給你,用 SSE 傳。
要不要用 stream,看 agent card 裡的 capabilities.streaming 是不是 true。寫著 true 才代表對方支援串流回覆,否則就老實用 message/send 排隊等結果。
四個 method、四個核心概念,加上一次往返實際長什麼樣,大概是 A2A 協定「做什麼」的全貌。它不做什麼、邊界畫在哪裡,是明天的主題。